For Tool Callers

This guide helps you discover and use UTCP tools in your applications, whether you’re building an AI agent, a workflow automation system, or any other tool-consuming application.

The Discovery Process

Using tools with UTCP follows this simple process:

  1. Connect to a tool provider’s discovery endpoint
  2. Retrieve the UTCPManual containing available tools
  3. Register the tools in your client
  4. Call tools as needed using their native protocols

Using the UTCP Client

The utcp package provides a ready-to-use client for discovering and calling tools. You can configure it with variables and even auto-load providers:

import asyncio
from utcp import UtcpClient
from utcp.models import Provider

async def main():
    # Create a UTCP client
    client = UtcpClient()
    
    # Define the provider
    provider = HttpProvider(
        name="weather_api",
        provider_type="http",
        url="https://api.example.com/utcp",
        http_method="GET"
    )
    
    # Register tools from the provider
    tools = await client.register_tool_provider(provider)
    print(f"Registered {len(tools)} tools from {provider.name}")
    
    # Call a tool with arguments
    result = await client.call_tool("weather_api.get_weather", arguments={"location": "San Francisco"})
    print(f"Weather: {result['temperature']}°C, {result['conditions']}")

if __name__ == "__main__":
    asyncio.run(main())

Tool Namespacing

UTCP uses a simple namespacing convention to avoid name conflicts:

provider_name.tool_name

For example, if a provider named “weather_api” offers a tool named “get_forecast”, you would call it using weather_api.get_forecast.

Understanding Tool Definitions

When you register a provider, you receive tool definitions that include:

Here’s what a typical tool definition looks like:

{
  "name": "get_weather",
  "description": "Get current weather for a location",
  "inputs": {
    "type": "object",
    "properties": { "location": { "type": "string", "description": "City name" } },
    "required": ["location"]
  },
  "outputs": {
    "type": "object",
    "properties": { "temperature": {"type": "number"}, "conditions": {"type": "string"} }
  },
  "tool_provider": {
    "name": "weather_api",
    "provider_type": "http",
    "url": "https://api.example.com/api/weather",
    "http_method": "GET"
  }
}

Error Handling Best Practices

When working with UTCP tools, implement these error handling strategies:

Provider Registration Errors:

Tool Calling Errors:

try:
    result = await client.call_tool("weather_api.get_weather", arguments={"location": "San Francisco"})
except UTCPProviderError as e:
    print(f"Provider error: {e}")
except UTCPToolNotFoundError as e:
    print(f"Tool not found: {e}")
except UTCPValidationError as e:
    print(f"Invalid parameters: {e}")

Working with Different Provider Types

UTCP supports multiple provider types, and the client handles the protocol differences for you:

The UTCPClient automatically uses the appropriate protocol based on its tool_provider configuration.

Advanced Usage

Authentication

The UTCP client supports various authentication methods through provider configuration:

from utcp import UtcpClient
from utcp.shared.provider import HttpProvider

client = UtcpClient()

# API Key authentication
provider_with_api_key = HttpProvider(
    name="weather_api",
    provider_type="http",
    url="https://api.example.com/utcp",
    http_method="GET",
    auth={
        "auth_type": "api_key",
        "api_key": "$YOUR_API_KEY",
        "var_name": "X-API-Key"
    }
)

# OAuth2 authentication
provider_with_oauth = HttpProvider(
    name="translation_api",
    provider_type="http",
    url="https://translate.example.com/utcp",
    http_method="GET",
    auth={
        "auth_type": "oauth2",
        "client_id": "$YOUR_CLIENT_ID",
        "client_secret": "$YOUR_CLIENT_SECRET",
        "token_url": "https://auth.example.com/token"
    }
)

# Register both providers
await client.register_tool_provider(provider_with_api_key)
await client.register_tool_provider(provider_with_oauth)

Client Configuration

For more complex applications, you can configure the UtcpClient using a configuration object. This allows you to externalize provider definitions and manage variables more effectively.

You can initialize the client by passing configuration parameters directly:

from utcp import UtcpClient

# Initialize client with configuration parameters
client = await UtcpClient.create(
    providers_file_path="providers.json",
    load_variables_from=[
        {"type": "dotenv", "env_file_path": ".env"}
    ]
)

# Tools are loaded automatically from the providers file
await client.load_tools_from_providers()

Provider Configuration File (providers.json)

The providers_file_path points to a JSON file containing a list of manual provider configurations. This allows you to manage your providers without hardcoding them in your application.

Example providers.json:

[
  {
    "name": "weather_api",
    "provider_type": "http",
    "url": "https://api.example.com/utcp",
    "http_method": "GET",
    "auth": {
      "auth_type": "api_key",
      "api_key": "$WEATHER_API_KEY",
      "var_name": "X-API-Key"
    }
  }
]

Variable Management and Substitution

The load_variables_from option allows you to load variables from external sources, such as a .env file. The client will automatically substitute variables in your provider configurations.

Example .env file:

WEATHER_API_KEY="your-secret-api-key"

This approach helps keep your secrets and other configuration values secure and separate from your codebase.

Working with Multiple Providers

You can register and use multiple providers in the same client:

# Register multiple providers
providers = [
    HttpProvider(name="weather_api", provider_type="http", url="https://weather.example.com/utcp", http_method="GET"),
    HttpProvider(name="translate_api", provider_type="http", url="https://translate.example.com/utcp", http_method="GET"),
    HttpProvider(name="image_api", provider_type="http", url="https://image.example.com/utcp", http_method="GET")
]

for provider in providers:
    tools = await client.register_tool_provider(provider)
    print(f"Registered {len(tools)} tools from {provider.name}")

# Access tools from different providers
weather = await client.call_tool("weather_api.get_forecast", arguments={"location": "Tokyo"})
translation = await client.call_tool("translate_api.translate", arguments={"text": "Hello", "target": "fr"})

Deregistering Providers

You can deregister providers when they’re no longer needed:

# Deregister a provider by name
await client.deregister_tool_provider("weather_api")

Advanced Customization

The UtcpClient is designed to be extensible. For advanced use cases, you can replace its core components with your own custom implementations.